Automatisation des tests

Introduction

Qui suis je ?

whoami

Déroulement du cours

  • N’hésitez pas à interrompre ou à intervenir

  • Merci de ne pas faire de bruit :)

Le contrôle continu

  • Comment ? Un questionnaire à choix multiples

  • Quand ? La dernière heure avec Richard Liot

Les sources

Découvrez l’importance des tests

Je viens de rework une fonction de 900 lignes sans aucun test

ci fire

Coder c’est tester !

  • Tester ce n’est pas que vérifier que son application marche!

    • C’est savoir rapidement quand l’application ne marche plus

    • où dans le code

    • et pourquoi

  • Construire sa couverture de tests = construire ses TNR (Tests de Non Régression)

Apprendre à coder c’est apprendre à tester

  • Impact sur la conception

  • Modularité & testabilité

XP pour eXtreme Programming

  • La méthodologie eXtreme Programming est une méthode de gestion de projet qui applique à l’extrême les principes de ceux des méthodes Agiles.

    • on se concentre sur les besoins du client ;

    • mise en place d’un développement itératif (sprints courts de 2/3 semaines) et de l’intégration continue.

  • La méthode XP s’appuie sur :

    • une forte réactivité au changement des besoins du client ;

    • un travail d’équipe ;

    • la qualité du travail fourni ;

    • la qualité des tests effectués au plus tôt.

TDD pour Test Driven Development

  • Il s’agit d’une technique de conception où le programmeur écrit d’abord le test avant de produire le moindre code.

  • Le développeur écrit ensuite le code pour que le test passe.

  • Une fois son test finalisé, il pourra être libre de refactorer autant qu’il le souhaite jusqu’à obtenir un code « propre ».

  • C’est une idée simple mais complexe à mettre en oeuvre.

  • Courbe d’apprentissage plus lente de prime abord.

Pourquoi ces pratiques ?

  • Vérifier la bonne compréhension des fonctionnalités

  • Meilleure couverture de tests automatisés

  • Facilité d’écriture des tests avant le code « métier »

  • Ils servent à promouvoir et vérifier la qualité et la fiabilité du code

    • Enfin, jusqu’à une certaine limite !!

dilbert tdd

Comment appliquer XP et TDD ?

  • Ecriture d’un test pour une fonctionnalité

  • Le test est « failed »

  • Codage de la fonctionnalité minimale

  • Vérification du cas passant

  • Répéter l’opération en enrichissant la fonctionnalité en refactorisant

Red / Green / Refactor

tdd

Rappels

Les tests unitaires

  • Les tests unitaires consistent à tester individuellement les composants d’une application.

  • On pourra ainsi valider la qualité du code et les performances d’un module.

Les tests d’intégration

  • Ces tests sont exécutés pour valider l’intégration des différents modules entre eux et dans leur environnement d’exploitation définitif.

  • Ils permettront de mettre en évidence des problèmes d’interfaces entre différents programmes.

Les tests fonctionnels

  • Ces tests ont pour but de vérifier la conformité de l’application développée avec le cahier des charges initial.

  • Ils sont donc basés sur les spécifications fonctionnelles et techniques.

  • L’écriture de tests fonctionnels automatisés représente un effort important.

Les tests d’acceptation

  • Que pouvez-vous accepter pour valider une fonctionnalité ?

    • Conformité des fonctionnalités demandées.

    • Les temps de réponses sont-ils corrects (chargement d’une page HTML, réponse d’une API, …​) ?

Les tests de charge et de performance

  • Ce sont des tests permettant de mesurer les temps de réponses du système en fonction des sollicitations.

  • Les tests de charge simulent un nombre prédéfini d’utilisateurs en simultané pour mesurer le dimensionnement de l’infrastructure nécessaire (serveurs, bande passante sur le réseau, …​)

  • Les tests de performance permettent de récupérer des métriques (temps de réponses, percentile)

90 percentile response time

Les tests en boîte noire, grise ou blanche

  • Les tests en « boite noire » consistent à examiner uniquement les fonctionnalités d’une application.

  • Les tests en « boîte blanche » consistent à examiner le fonctionnement d’une application et sa structure interne, ses processus, plutôt que ses fonctionnalités.

  • Les tests en « boîte grise » compilent ces deux précédentes approches : ils éprouvent à la fois les fonctionnalités et le fonctionnement d’un système.

black grey white testing

Analogie des « boîtes » en comparant le système testé à une voiture.

  • En méthode « boîte noire », on vérifie que la voiture fonctionne en allumant les lumières, en klaxonnant et en tournant la clé pour que le moteur s’allume. Si tout se passe comme prévu, la voiture fonctionne.

  • En méthode « boîte blanche », on emmène la voiture chez le garagiste, qui regarde le moteur ainsi que toutes les autres parties (mécaniques comme électriques) de la voiture. Si elle est en bon état, elle fonctionne.

  • En méthode « boîte grise », on emmène la voiture chez le garagiste, et en tournant la clé dans la serrure, on vérifie que le moteur s’allume, et le garagiste observe en même temps le moteur pour s’assurer qu’il démarre bien selon le bon processus.

Pyramide des tests

pyramide testing

Proportions des tests

reversed pyramid testing

Les frameworks de tests

Les frameworks pour les tests unitaires

  • JUnit, TestNG (java)

  • Jasmine, Karma, Mocha (javascript)

  • nUnit (.Net)

  • SimpleTest (PHP)

  • dUnit (Delphi)

  • cppUnit (C++) etc..

unit tests frameworks

Les frameworks pour le mocking

  • Mockito, EasyMock, PowerMock (java)

  • Sinon JS, Jest, Rhino Mocks (javascript)

  • TypeMock, Moq (.Net)

mock tests frameworks

Les outils de tests de charge

  • JMeter, Gatling, Taurus, Locust

performance frameworks

Les outils d’analyse de couverture de tests

  • Cobertura, JaCoCo (java)

  • Coverage.py (python)

  • Bullseye Coverage (C++, C)

  • NCover, dotCover (.Net)

coverage tests tools

Et bien d’autres

  • Tests manuels (SoapUI, Postman)

  • Tests d’API (frameworks REST Assured, Karate)

  • Outils de tests de sécurité

    • type SAST (Source Code Analysis Tools)

    • type DAST (Dynamic Application Security Testing)

  • Tests IHM (GUI testing) comme Seleniumn, QTP ou Cucumber

  • …​

L’automatisation

automation

Plateforme Intégration Continue

  • PIC : Plateforme agrégeant des outils permettant l’IC

cip

Job classique

commit build

Principaux avantages

  • Test immédiat des modifications

  • Notification rapide en cas de problèmes

  • Les problèmes d’intégration sont détectés et réparés de façon continue

Les difficultés lors de la mise en oeuvre de tests unitaires

  • réticences à la mise en oeuvre

  • difficultés de rédaction et de codage

  • couverture du code testé

  • temps nécessaire à la rédaction des cas de tests

  • véracité des cas de tests

  • temps nécessaire à la maintenance des cas de tests

  • les cas de tests doivent être répétables

  • complexité ⇒ base de données, fichiers

  • …​

JUnit 4 les annotations

JUnit 4La description

@BeforeClass

La méthode est exécutée une fois avant le début de tous les tests.

@AfterClass

La méthode est exécutée une fois tous les tests joués.

@Ignore or @Ignore("Why disabled")

Marque que le test doit être désactivé.

@Test (expected = Exception.class)

Échec si la méthode ne lance pas l’exception nommée.

@Test(timeout=100)

Échoue si la méthode prend plus de 100 millisecondes.

JUnit 4La description

import org.junit.*

Package pour l’utilisation des annotations.

@Test

Identifie une méthode en tant que méthode de test.

@Before

Exécuté avant chaque test (identifié par une méthode).

@After

Exécuté après chaque test (chaque méthode).

JUnit 4 les assertions

DéclarationLa description

fail([message])

Laissez la méthode échouer.

assertTrue([message,] boolean condition)

Vérifie que la condition booléenne est vraie.

assertFalse([message,] boolean condition)

Vérifie que la condition booléenne est fausse.

assertEquals([message,] expected, actual)

Teste que deux valeurs sont identiques.

assertEquals([message,] expected, actual, tolerance)

Vérifiez que les valeurs float ou double correspondent.

  • Besoin de plus de puissance ? utiliser AssertJ ou Hamcrest.

import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.containsString;
...
assertThat("Hello world", containsString("world"));
DéclarationLa description

assertNull([message,] object)

Vérifie que l’objet est nul.

assertNotNull([message,] object)

Vérifie que l’objet n’est pas nul.

assertSame([message,] expected, actual)

Vérifie que les deux variables se réfèrent au même objet.

assertNotSame([message,] expected, actual)

Vérifie que les deux variables se réfèrent à des objets différents.

JUnit 5 - plus flexible

  • une API pour écrire des tests

  • un mécanisme pour découvrir et lancer les tests

  • une API pour lancer les tests (pour les outils comme Eclipse)

Ce qui change (résumé)JUnit 4JUnit 5

package

org.junit

org.junit.jupiter.api

plus de classes/méthodes publiques

public class MyTest

class MyTest

quelques annotations

@Ignore, @Category

@Disable, @Tag

les assertions

org.junit.Assert

org.junit.jupiter.api.Assertions

ordre des paramètres

assertEquals("Error message", expected, actual)

assertEquals(expected, actual, "Error message")

suppression de assertThat

org.junit.Assert.assertThat

Utiliser directement la librairie Hamcrest

CRAFTSMAN RECIPES : SOIGNEZ VOS TESTS UNITAIRES

  • Comment donner du sens à vos tests unitaires ?

    • En appliquant certains principes du Behavior Driven Development (BDD)

  • Pourquoi ?

    • Afin d’obtenir une classe de tests unitaires claire et maintenable.

  • Les tests doivent être

    • compréhensibles, lisibles et facilement modifiables

    • automatisables, répétables et exécutés rapidement

TP

fast typing computer

C’est à vous ;)

Comment faciliter l’écriture de tests unitaires

  • Mockito est un framework Java, permettant :

    • de mocker ou espionner des objets,

    • simuler et vérifier des comportements,

    • ou encore simplifier l’écriture de tests unitaires.

A quoi sert un mock ?

  • Exemple dans une Architecture Hexagonale sur les principes du Domain Driven Development (DDD) :

650

Domain Driven Development (DDD)

  • L’approche DDD vise, à isoler un domaine métier avec les caractéristiques suivantes:

    • Approfondissement des règles métier spécifiques en accord avec le modèle d’entreprise, la stratégie et les processus métier.

    • Isolation des autres domaines métier et des autres couches de l’architecture de l’application.

    • Modèle construit avec un couplage faible avec les autres couches de l’application.

    • Facilement maintenable, testable et versionnable.

    • Modèle conçu avec le moins de dépendances possibles avec une technologie ou un framework.

Architecture Hexagonale

« Permettre à une application d’être pilotée aussi bien par des utilisateurs que par des programmes, des tests automatisés ou des scripts batchs, et d’être développée et testée en isolation de ses éventuels systèmes d’exécution et bases de données. »

  • L’architecture hexagonale repose sur trois principes et techniques:

    • Séparer explicitement la logique métier de la partie exposition (client-side) et persistence (server-side).

    • Les dépendances partent des couches techniques (client-side / server-side) vers la couche logique métier

    • Il faut isoler les couches en utilisant des ports et des adaptateurs

Approche Behavior Driven Development (BDD)

  • En effet, il sera très intuitif d’écrire son test en suivant la notion //Given //When //Then, et nous verrons que Mockito met l’accent sur la 1ère et la 3ème notion.

Greffer Mockito sur une classe JUnit

Deux possibilités :

  • Ajouter l’annotation @RunWith comme suit :

@RunWith(MockitoJunitRunner.class)
public class MyTestClass {

}
  • Ou à l’initialisation dans la méthode d’initialisation (ici setUp())

private AutoCloseable closeable;
...


@Before
public void setUp() {
    closeable = MockitoAnnotations.openMocks(this);
}

@After
public void tearDown() throws Exception {
    closeable.close();
}
  • Il est conseillé de libérer la ressource après chaque test (voir méthode tearDown()).

Le stubbing

Mockito est capable de « stubber » (bouchonner) des classes concrètes mais aussi des interfaces.

  • On peut appeler la méthode mock(…​) sur une classe :

User user = Mockito.mock(User.class);
  • Ou placer une annotation si la variable est en instance de classe

@Mock
User user;

Définition du comportement des objets mockés ou « Stubbing »

Retour d’une valeur unique

Mockito.when(user.getLogin()).thenReturn(‘user1’);

Faire appel à la méthode d’origine

Mockito.when(user.getLogin()).thenCallRealMethod();

Levée d’exceptions

Mockito.when(user.getLogin()).thenThrow(new RuntimeException());

Espionner un objet avec @Spy

  • La différence entre @Mock et @Spy réside dans le fait que la deuxième permet d’instancier l’objet mocké; on peut ainsi effectuer un mock partiel.

  • Quand on appelle une méthode de l’objet « espionné »

    • la vraie méthode est appelée,

    • à moins qu’un comportement ai été défini.

@Spy
User user = new User(‘user1’);

user.getLogin() // retourne user1
Mockito.when(user.getPassword()).thenReturn(‘top secret’);

Vérification d’interactions

verify(user).getLogin();

// le test passe si getLogin() est appelée avant la fin du timeout (ici 100 ms)
verify(user, timeout(100)).getLogin();

// le test passe si il n'existe aucune autre interaction sur le mock (non vérifiée)
verifyNoMoreInteractions(luser);

Injection

  • Mockito permet également d’injecter des ressources (classes nécessaires au fonctionnement de l’objet mocké), en utilisant l’annotation @InjectMock.

  • L’injection des mocks dans l’objet marqué par @InjectMock se fera (par ordre de priorité) :

    • injection par le constructeur

    • injection par la méthode de type « setter »

    • injection par l’attribut (même si celui-ci est private)

TP

ouvrir le pdf tp/tp-mocks/tp-mocks.pdf

fast typing computer

C’est à vous ;)

Q&A

question comments concerns